package fr.ece.ing4.si.singlethread;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;


/**
 * 
 * @author Nico
 * This is the MonteCarlo class with access to the methods which does the computation
 */

public class MonteCarlo {
	
	private String CallPutFlag;
	private double S;
	private double X;
	private double T;
	private double r;
	private double b;
	private double v;
	private int nSteps;
	private int nSimulations;
	public static int progression;
	
	/**
	 * Default Constructor
	 */
	public MonteCarlo(){
	}
	/**
	 * Constructor which set args
	 * @param callPutFlag
	 * @param s
	 * @param x
	 * @param t
	 * @param r
	 * @param b
	 * @param v
	 * @param nSteps
	 * @param nSimulations
	 */
	public MonteCarlo(String callPutFlag, double s, double x, double t,
			double r, double b, double v, int nSteps, int nSimulations) {
		CallPutFlag = callPutFlag;
		S = s;
		X = x;
		T = t;
		this.r = r;
		this.b = b;
		this.v = v;
		this.nSteps = nSteps;
		this.nSimulations = nSimulations;
	}
	
	/**
	 * 
	 * @return an ArrayList of object MonteCarlo set to the values retrieved from the file
	 * @throws IOException
	 */
	public static ArrayList<MonteCarlo> ReadData() throws IOException{
		final BufferedReader reader = 
			      new BufferedReader(new FileReader("data.txt"));
		final ArrayList<MonteCarlo> data = new ArrayList<MonteCarlo>();
		
		String dataTemp = null;
		int i=0;
	    while((dataTemp = reader.readLine()) != null) {
	      final String[] line = dataTemp.split(", ");
	      MonteCarlo mCarlo = new MonteCarlo();
	      mCarlo.CallPutFlag = line[i];
	      mCarlo.S = Double.valueOf(line[++i]);
	      mCarlo.X = Double.valueOf(line[++i]);
	      mCarlo.r = Double.valueOf(line[++i]);
	      mCarlo.T = Double.valueOf(line[++i]);
	      mCarlo.b = Double.valueOf(line[++i]);
	      mCarlo.v = Double.valueOf(line[++i]);
	      mCarlo.nSteps = Integer.valueOf(line[++i]);
	      mCarlo.nSimulations = Integer.valueOf(line[++i]);
	      data.add(mCarlo);
	      i=0;
	    }
	    reader.close();
		return data;
	}
	/**
	 * Calculate the value of Montecarlo SingleThreaded Simulation
	 * @return the value of the MonteCarloStandard option for The MonteCarlo object (Single Threaded method)
	 */
	public double MonteCarloStandardOption(){
		
		double dt, St;
		double Sum = 0.0, Drift, vSqrdt;
		int i, j, z=0;
		
		dt = this.T / this.nSteps;
		Drift = (this.b - Math.pow(this.v,2) / 2) * dt;
		vSqrdt = this.v * Math.sqrt(dt);
		
		if(this.CallPutFlag.equals("c"))
		{
			z=1;
		}
		else if(this.CallPutFlag.equals("p"))
		{
			z=-1;
		}
		
		for(i=1;i<=this.nSimulations;i++){
			St = this.S;
			for(j=1;j<=this.nSteps;j++){
				St = St * Math.exp(Drift + vSqrdt * (new Random().nextGaussian()));
			}
			Sum = Sum + Math.max(z*(St - this.X), 0);
			progression++;
		}
		
		return Math.exp(-this.r * this.T) * (Sum / this.nSimulations);
	}
	
	/**
	 * Calculate the value of Montecarlo Multithreaded Simulation
	 * @return the value of the MonteCarloStandard option for The MonteCarlo object (Multi Threaded method)
	 * @throws InterruptedException
	 * @throws ExecutionException
	 */	
	public double ConcurrentMonteCarloStandardOption() throws InterruptedException, ExecutionException{
		
		double dt;
		double Sum = 0.0;
		final double Drift;
		final double vSqrdt;
		int i, z=0;
		
		final int numberOfCores = Runtime.getRuntime().availableProcessors();
	    final double blockingCoefficient = 0.1;
	    final int poolSize = (int)(numberOfCores / (1 - blockingCoefficient));
	    
	    System.out.println("Number of Cores available is " + numberOfCores);
	    System.out.println("Pool size is " + poolSize);
	    final List<Callable<Double>> partitions = new ArrayList<Callable<Double>>();
		
		dt = this.T / this.nSteps;
		Drift = (this.b - Math.pow(this.v,2) / 2) * dt;
		vSqrdt = this.v * Math.sqrt(dt);
		
		if(this.CallPutFlag.equals("c"))
		{
			z=1;
		}
		else if(this.CallPutFlag.equals("p"))
		{
			z=-1;
		}
		
		final double zf = z;
		
		for(i=1;i<=this.nSimulations;i++){
			partitions.add(new Callable<Double>() {
				@Override
				public Double call() throws Exception {
					
					double St = S;
					for(int j=1;j<=nSteps;j++){
						St = St * Math.exp(Drift + vSqrdt * (new Random().nextGaussian()));
					}
					synchronized (partitions) {
						progression++;
					}
					return Math.max(zf*(St - X), 0); 
				}
			});
		}
		
		final ExecutorService executorPool = Executors.newFixedThreadPool(poolSize);
		final List<Future<Double>> valueOfReturn = executorPool.invokeAll(partitions, 10000, TimeUnit.SECONDS);
		
		for(final Future<Double> value: valueOfReturn)
			Sum+= value.get();
		
		executorPool.shutdown();
		
		return Math.exp(-this.r * this.T) * (Sum / this.nSimulations);
	}
	
	public String getCallPutFlag() {
		return CallPutFlag;
	}

	public void setCallPutFlag(String callPutFlag) {
		CallPutFlag = callPutFlag;
	}

	public double getS() {
		return S;
	}

	public void setS(double s) {
		S = s;
	}

	public double getX() {
		return X;
	}

	public void setX(double x) {
		X = x;
	}

	public double getT() {
		return T;
	}

	public void setT(double t) {
		T = t;
	}

	public double getR() {
		return r;
	}

	public void setR(double r) {
		this.r = r;
	}

	public double getB() {
		return b;
	}

	public void setB(double b) {
		this.b = b;
	}

	public double getV() {
		return v;
	}

	public void setV(double v) {
		this.v = v;
	}

	public int getnSteps() {
		return nSteps;
	}

	public void setnSteps(int nSteps) {
		this.nSteps = nSteps;
	}

	public int getnSimulations() {
		return nSimulations;
	}

	public void setnSimulations(int nSimulations) {
		this.nSimulations = nSimulations;
	}
	
	/**
	 * Override toString function for MonteCarlo object to display in the console
	 */
	@Override
	public String toString(){
		return "Parameters: CallPut: " + this.CallPutFlag + ", S: " + this.S + ", X: " + this.X + ", T: " + this.T + ", r: " + 
	this.r + ", b: " + this.b + ", v: " + this.v + ", nSteps: " + this.nSteps + ", nSimul: " + this.nSimulations;
		
	}
}
